"""
Copyright © 2023 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/micropython-mini-calendar
"""
__version__ = '0.1.5'
__version_info__ = (0, 1, 5)
print('module ecalendar version:', __version__)


import time
from machine import Pin
from neopixel import NeoPixel
from utils.utilities import Utilities
from utils.wifihandler import WifiHandler
from drivers.photoresistor import Photoresistor

# https://gitee.com/walkline/micropython-timer-dispatcher
from dispatcher import Dispatcher


CONFIG = Utilities.import_config()


# 伽马校正查询表，伽马值2.2
# https://victornpb.github.io/gamma-table-generator/

# region Constants
# 显示月份需要用到的 LED 的编号范围
MONTHS_RANGE = range(13, 25)

# 根据每月第一天的星期确定显示星期需要用到的 LED 的编号范围
WEEKDAYS_RANGE = {
	0: range(6, 13),
	1: range(5, 12),
	2: range(4, 11),
	3: range(3, 10),
	4: range(2, 9),
	5: range(1, 8),
	6: range(0, 7),
}

# 根据每月第一天的星期确定显示日期需要用到的 LED 的编号范围，每月按 31 天取值
DAYS_RANGE = {
	0: (range(0, 7), range(13, 20), range(26, 33), range(39, 46), range(52, 55)),
	1: (range(0, 6), range(12, 19), range(25, 32), range(38, 45), range(51, 55)),
	2: (range(0, 5), range(11, 18), range(24, 31), range(37, 44), range(50, 55)),
	3: (range(0, 4), range(10, 17), range(23, 30), range(36, 43), range(49, 55)),
	4: (range(0, 3), range(9, 16), range(22, 29), range(35, 42), range(48, 55)),
	5: (range(0, 2), range(8, 15), range(21, 28), range(34, 41), range(47, 54), range(56, 57)),
	6: (range(0, 1), range(7, 14), range(20, 27), range(33, 40), range(46,53), range(55, 57)),
}

# 每月按 31 天计算，不同月份需要缩短的天数，二月按 29 天取值
DAYS_SHORTEN_OF_MONTH = (0, 2, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0)

# 光敏电阻亮度等级与 LED 亮度百分比对照表
BRIGHTNESS_LEVEL = {
	Photoresistor.LEVEL_1: 60,
	Photoresistor.LEVEL_2: 50,
	Photoresistor.LEVEL_3: 40,
	Photoresistor.LEVEL_4: 30,
	Photoresistor.LEVEL_5: 20,
	Photoresistor.LEVEL_6: 10
}
# endregion


class ECalendarException(BaseException):
	pass


class DateTime(object):
	def __init__(self):
		self.__year =\
		self.__month =\
		self.__day =\
		self.__hour =\
		self.__minute =\
		self.__second =\
		self.__weekday = None

	def first_day_of_month(self) -> int:
		'''获取当月第一天的星期数(0~6)'''
		self.now()
		return time.localtime(time.mktime((self.__year, self.__month, 1, 0, 0, 0, 0, 0)))[6]

	def milliseconds_until_next_hour(self) -> int:
		'''获取到下一个整点之前的毫秒数'''
		self.now()
		return (time.mktime((self.__year, self.__month, self.__day, self.__hour + 1, 0, 0, 0, 0)) - time.time()) * 1000

	def milliseconds_until_midnight(self) -> int:
		'''获取到第二天零点之前的毫秒数'''
		self.now()
		return (time.mktime((self.__year, self.__month, self.__day + 1, 0, 0, 0, 0, 0)) - time.time()) * 1000

	def is_leap_year(self) -> bool:
		self.now()
		return (self.__year % 4 == 0 and self.__year % 100 != 0) or self.__year % 400 == 0

	def format_ms(self, seconds) -> str:
		'''将毫秒数转换为可读时间'''
		seconds //= 1000
		second = seconds % 60
		minutes = (seconds - second) // 60
		minute = minutes % 60
		hour = (minutes - minute) // 60

		return f'{hour:02d}:{minute:02d}:{second:02d}'

	def now(self) -> None:
		self.__year,\
		self.__month,\
		self.__day,\
		self.__hour,\
		self.__minute,\
		self.__second,\
		self.__weekday,\
		_ = time.localtime() # (year, month, mday, hour, minute, second, weekday, yearday)

	def __str__(self):
		self.now()
		weekday = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
		return f'{self.__year}-{self.__month:02d}-{self.__day:02d} {self.__hour:02d}:{self.__minute:02d}:{self.__second:02d} ({weekday[self.__weekday]})'


class ECalendar(DateTime):
	def __init__(self):
		self.__np_days = NeoPixel(Pin(CONFIG.PINS.DIN_DAYS), CONFIG.WS2812.DAYS_COUNT)
		self.__np_week_month = NeoPixel(Pin(CONFIG.PINS.DIN_WEEK_MONTH), CONFIG.WS2812.WEEK_COUNT + CONFIG.WS2812.MONTH_COUNT)

		self.__tasks = Dispatcher()
		self.__adc = Photoresistor(CONFIG.PINS.BRIGHTNESS_ADC)

		self.__days_list = []
		self.__months_list = list(MONTHS_RANGE)
		self.__weekdays_list = []

		self.__last_adc_level = 0
		self.__bright_percent = 100

		self.__task_sync_time = lambda: self.__sync_time_cb()
		self.__task_auto_brightness = lambda: self.__auto_brightness_cb()

	def start(self):
		self.__sync_time_cb()
		self.__auto_brightness_cb()

		self.__tasks.add_work(self.__task_auto_brightness, CONFIG.PERIOD.ADC_MS)

	def stop(self):
		self.__tasks.del_works()
		self.clean()

	def clean(self, immediately=True):
		self.__np_days.fill(CONFIG.COLORS.BLACK)
		self.__np_week_month.fill(CONFIG.COLORS.BLACK)

		if immediately:
			self.__write()

	def show_date(self):
		'''刷新日历显示内容'''
		self.clean(False)
		self.__refresh_date_range_list()
		self.__show_days_rectange()

		self.now()
		self.__np_days[self.__days_list[self.__day - 1]] = self.__convert_color(CONFIG.COLORS.DAY)
		self.__np_week_month[self.__weekdays_list[self.__weekday]] = self.__convert_color(CONFIG.COLORS.WEEKDAY)
		self.__np_week_month[self.__months_list[self.__month - 1]] = self.__convert_color(CONFIG.COLORS.MONTH)

		self.__write()

	def show_connecting(self, mode):
		'''显示联网/配网动画'''
		animation = self.__animation_generator(mode)

		if mode == 0:
			self.__tasks.add_work(self.__connecting_cb, 50, animation, mode)
		elif mode == 1:
			self.__tasks.add_work(self.__connecting_cb, 200, animation, mode)

	def __sync_time_cb(self):
		'''每次整点进行一次联网校时并更新日历显示内容'''
		Utilities.sync_time()

		self.__tasks.add_work(self.__task_sync_time, self.milliseconds_until_next_hour())
		print('sync time after:', self.format_ms(self.milliseconds_until_next_hour()))

		self.now()
		self.show_date()

	def __connecting_cb(self, animation, mode):
		if mode == 0: # 配网指示
			for _ in MONTHS_RANGE:
				self.__np_week_month[_] = CONFIG.COLORS.BLACK

			self.__np_week_month[next(animation)] =\
				self.__convert_color(CONFIG.COLORS.SKYBLUE if WifiHandler.is_ble_mode() else CONFIG.COLORS.LIGHTGREEN)
		elif mode == 1: # 联网指示
			color = self.__convert_color(next(animation))

			for _ in MONTHS_RANGE:
				self.__np_week_month[_] = (color, color, color)

		self.__np_week_month.write()

	def __auto_brightness_cb(self):
		'''自动亮度回调函数'''
		adc_level = self.__adc.level

		if self.__last_adc_level == adc_level:
			return

		self.__last_adc_level = adc_level
		self.__bright_percent = BRIGHTNESS_LEVEL[adc_level]

		self.show_date()
		print(f'brightness level: {adc_level} ({self.__bright_percent}%)')

	def __write(self):
		self.__np_days.write()
		self.__np_week_month.write()

	def __animation_generator(self, mode):
		if mode == 0: # 配网指示
			while True:
				for _ in MONTHS_RANGE[:6]:
					yield _

				for _ in MONTHS_RANGE[-1:-7:-1]:
					yield _
		elif mode == 1: # 联网指示
			color = 0
			max_color = int(255 * CONFIG.BRIGHTNESS.MAX / 100)
			step = max_color // 6

			if step <= 0:
				step = 1

			while True:
				yield color

				color += step

				if color >= max_color or color <= 0:
					step *= -1

	def __convert_color(self, color:tuple):
		'''转换颜色亮度值'''
		if isinstance(color, tuple) and len(color) == 3:
			h, s, v = self.__rgb_to_hsv(*color)
			v *= (CONFIG.BRIGHTNESS.MAX / 100) * (self.__bright_percent / 100)
			color = self.__hsv_to_rgb(h, s, v)

		return color

	# rgb 颜色转 hsv
	def __rgb_to_hsv(self, r:int, g:int, b:int) -> tuple:
		r, g, b = r / 255.0, g / 255.0, b / 255.0
		max_value = max(r, g, b)
		min_value = min(r, g, b)
		delta = max_value - min_value
		if delta == 0:
			return 0, 0, max_value
		elif max_value == r:
			h = (g - b) / delta % 6
		elif max_value == g:
			h = (b - r) / delta + 2
		elif max_value == b:
			h = (r - g) / delta + 4
		h = h * 60
		if h < 0:
			h += 360
		s = delta / max_value
		v = max_value

		return h, s, v

	# hsv 转 rgb 颜色
	def __hsv_to_rgb(self, h:float, s:float, v:float) -> tuple:
		if s == 0:
			r, g, b = v, v, v
		else:
			h = h / 60.0
			i = int(h)
			f = h - i
			p = v * (1 - s)
			q = v * (1 - s * f)
			t = v * (1 - s * (1 - f))
			if i == 0:
				r, g, b = v, t, p
			elif i == 1:
				r, g, b = q, v, p
			elif i == 2:
				r, g, b = p, v, t
			elif i == 3:
				r, g, b = p, q, v
			elif i == 4:
				r, g, b = t, p, v
			else:
				r, g, b = v, p, q

		return int(r * 255), int(g * 255), int(b * 255)

	def __show_days_rectange(self):
		'''高亮显示所需的日期区域，可用于定位前盖板移动位置'''
		for index in self.__days_list:
			self.__np_days[index] = CONFIG.COLORS.DAYS_BG

		for index in self.__weekdays_list:
			self.__np_week_month[index] = self.__convert_color(CONFIG.COLORS.WEEKDAYS_BG)

	def __refresh_date_range_list(self):
		'''刷新显示日期和星期需要用到的 LED 列表'''
		self.__days_list = []
		self.__weekdays_list = []

		for part in DAYS_RANGE[self.first_day_of_month()]:
			self.__days_list += list(part)

		for _ in range(DAYS_SHORTEN_OF_MONTH[self.__month - 1]):
			self.__days_list.pop()

		if self.__month == 2 and not self.is_leap_year():
			self.__days_list.pop()

		self.__weekdays_list = list(WEEKDAYS_RANGE[self.first_day_of_month()])


if __name__ == '__main__':
	from utils.wifihandler import WifiHandler

	ecalendar = ECalendar()

	ecalendar.clean()
	ecalendar.show_connecting(0)

	WifiHandler.set_sta_mode(timeout_sec=120)

	ecalendar.stop()
	ecalendar.start()
